﻿/*########################################################################
    Filename:     MenuWndHook.cpp
    ----------------------------------------------------
    Remarks:    ...
    ----------------------------------------------------
    Author:        成真
    Email:        anyou@sina.com
                anyou@msn.com
    Created:    7/4/2003 17:38
                6/20/2012 黄建雄，修改为使用SSkin皮肤绘制非客户区边框，支持边框大小配置
  ########################################################################*/

#include "souistd.h"
#include "helper/SMenuWndHook.h"
#include "res.mgr/SSkinPool.h"
#include "core/SSkin.h"

#ifdef _WIN64
#define GWL_WNDPROC GWLP_WNDPROC
#endif //_WIN64

#ifdef _WIN32

SNSBEGIN

const TCHAR CoolMenu_oldProc[] = _T("CoolMenu_oldProc");

#define SM_CXMENUBORDER 3 //默认菜单边框大小

/*########################################################################
              ------------------------------------------------
                                class CMenuWndHook
              ------------------------------------------------
  ########################################################################*/
SMap<HWND, SMenuWndHook *> SMenuWndHook::m_WndMenuMap;

HHOOK SMenuWndHook::m_hMenuHook = NULL;
SStringW SMenuWndHook::m_strSkinName;

SMenuWndHook::SMenuWndHook(HWND hWnd)
    : m_hWnd(hWnd)
{
}

SMenuWndHook::~SMenuWndHook()
{
    WNDPROC oldWndProc = (WNDPROC)::GetProp(m_hWnd, CoolMenu_oldProc);
    if (oldWndProc != NULL)
    {
        ::SetWindowLongPtr(m_hWnd, GWLP_WNDPROC, (LONG_PTR)oldWndProc);
        ::RemoveProp(m_hWnd, CoolMenu_oldProc);
    }
    m_WndMenuMap.RemoveKey(m_hWnd);
}

void SMenuWndHook::InstallHook(HINSTANCE hInst, LPCWSTR pszSkinName)
{
    if (m_hMenuHook == NULL)
    {
        m_hMenuHook = ::SetWindowsHookEx(WH_CALLWNDPROC, WindowHook, hInst, ::GetCurrentThreadId());
        m_strSkinName = pszSkinName;
    }
}

void SMenuWndHook::UnInstallHook()
{
    SPOSITION pos = m_WndMenuMap.GetStartPosition();
    while (pos)
    {
        SMap<HWND, SMenuWndHook *>::CPair *p = m_WndMenuMap.GetNext(pos);
        delete p->m_value;
    }
    m_WndMenuMap.RemoveAll();

    if (m_hMenuHook != NULL)
    {
        ::UnhookWindowsHookEx(m_hMenuHook);
    }
}

SMenuWndHook *SMenuWndHook::GetWndHook(HWND hwnd)
{

    SMap<HWND, SMenuWndHook *>::CPair *p = m_WndMenuMap.Lookup(hwnd);
    if (!p)
        return NULL;
    return p->m_value;
}

SMenuWndHook *SMenuWndHook::AddWndHook(HWND hwnd)
{
    SMenuWndHook *pWnd = GetWndHook(hwnd);
    if (pWnd)
        return pWnd;

    pWnd = new SMenuWndHook(hwnd);
    if (pWnd != NULL)
    {
        m_WndMenuMap[hwnd] = pWnd;
    }
    return pWnd;
}

/*########################################################################
              ------------------------------------------------
                                  消息过程
              ------------------------------------------------
  ########################################################################*/
LRESULT CALLBACK SMenuWndHook::WindowHook(int code, WPARAM wParam, LPARAM lParam)
{
    CWPSTRUCT *pStruct = (CWPSTRUCT *)lParam;

    while (code == HC_ACTION)
    {
        HWND hWnd = pStruct->hwnd;

        if (pStruct->message != WM_CREATE && pStruct->message != 0x01E2)
        {
            break;
        }

        // 是否为菜单类 ----------------------------------------
        TCHAR strClassName[10];
        int Count = ::GetClassName(hWnd, strClassName, sizeof(strClassName) / sizeof(strClassName[0]));
        if (Count != 6 || _tcscmp(strClassName, _T("#32768")) != 0)
        {
            break;
        }

        // 是否已经被子类化 ------------------------------------
        if (::GetProp(hWnd, CoolMenu_oldProc) != NULL)
        {
            break;
        }

        AddWndHook(pStruct->hwnd);

        // 取得原来的窗口过程 ----------------------------------
        WNDPROC oldWndProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC);
        if (oldWndProc == NULL)
        {
            break;
        }

        SASSERT(oldWndProc != CoolMenuProc);
        // 保存到窗口的属性中 ----------------------------------
        if (!SetProp(hWnd, CoolMenu_oldProc, (HANDLE)oldWndProc))
        {
            break;
        }

        // 子类化 ----------------------------------------------
        if (!SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)CoolMenuProc))
        {
            ::RemoveProp(hWnd, CoolMenu_oldProc);
            break;
        }

        break;
    }
    return CallNextHookEx(m_hMenuHook, code, wParam, lParam);
}

LRESULT CALLBACK SMenuWndHook::CoolMenuProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    WNDPROC oldWndProc = (WNDPROC)::GetProp(hWnd, CoolMenu_oldProc);
    SMenuWndHook *pWnd = NULL;

    switch (uMsg)
    {
    case WM_CREATE:
    {
        LRESULT lResult = CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam);
        if ((pWnd = GetWndHook(hWnd)) != NULL)
        {
            lResult = (LRESULT)pWnd->OnCreate((LPCREATESTRUCT)lParam);
        }
        return lResult;
    }
    break;
    case WM_NCCALCSIZE:
    {
        LRESULT lResult = CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam);
        if ((pWnd = GetWndHook(hWnd)) != NULL)
        {
            pWnd->OnNcCalcsize((BOOL)wParam, (NCCALCSIZE_PARAMS *)lParam);
        }
        return lResult;
    }
    break;
    case WM_WINDOWPOSCHANGING:
    {
        if ((pWnd = GetWndHook(hWnd)) != NULL)
        {
            pWnd->OnWindowPosChanging((LPWINDOWPOS)lParam);
        }
    }
    break;
    case WM_WINDOWPOSCHANGED:
    {
        LRESULT lResult = CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam);
        if ((pWnd = GetWndHook(hWnd)) != NULL)
        {
            pWnd->OnWindowPosChanged();
        }
        return lResult;
    }
    break;
    case WM_PRINT:
    {
        LRESULT lResult = CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam);
        if ((pWnd = GetWndHook(hWnd)) != NULL)
        {
            pWnd->OnPrint((HDC)wParam);
        }
        return lResult;
    }
    break;
    case WM_NCPAINT:
    {
        if ((pWnd = GetWndHook(hWnd)) != NULL)
        {
            pWnd->OnNcPaint();
            return 0;
        }
    }
    break;
    case WM_NCDESTROY:
    {
        if ((pWnd = GetWndHook(hWnd)) != NULL)
        {
            pWnd->OnNcDestroy();
        }
    }
    break;
    }
    return CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam);
}

/*########################################################################
              ------------------------------------------------
                                消息处理函数
              ------------------------------------------------
  ########################################################################*/
int SMenuWndHook::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    SetClassLong(m_hWnd, GCL_STYLE, GetClassLong(m_hWnd, GCL_STYLE) & ~CS_DROPSHADOW);
    return 0;
}

void SMenuWndHook::OnWindowPosChanging(WINDOWPOS *pWindowPos)
{
    if (m_strSkinName.IsEmpty())
        return;
    ISkinObj *pSkin = GETSKIN(m_strSkinName, 100);
    if (!pSkin || !pSkin->IsClass(SSkinImgFrame::GetClassName()))
        return;
    SSkinImgFrame *pBorderSkin = static_cast<SSkinImgFrame *>(pSkin);
    if (!pBorderSkin)
        return;
    pWindowPos->cx += pBorderSkin->GetMargin().left + pBorderSkin->GetMargin().right - SM_CXMENUBORDER * 2;
    pWindowPos->cy += pBorderSkin->GetMargin().top + pBorderSkin->GetMargin().bottom - SM_CXMENUBORDER * 2;
}

void SMenuWndHook::OnNcCalcsize(BOOL bValidCalc, NCCALCSIZE_PARAMS *lpncsp)
{
    if (m_strSkinName.IsEmpty())
        return;
    ISkinObj *pSkin = GETSKIN(m_strSkinName, 100);
    if (!pSkin || !pSkin->IsClass(SSkinImgFrame::GetClassName()))
        return;
    SSkinImgFrame *pBorderSkin = static_cast<SSkinImgFrame *>(pSkin);
    if (!pBorderSkin)
        return;

    lpncsp->rgrc[0].left = lpncsp->lppos->x + pBorderSkin->GetMargin().left;
    lpncsp->rgrc[0].top = lpncsp->lppos->y + pBorderSkin->GetMargin().top;
    lpncsp->rgrc[0].right = lpncsp->lppos->x + lpncsp->lppos->cx - pBorderSkin->GetMargin().right;
    lpncsp->rgrc[0].bottom = lpncsp->lppos->y + lpncsp->lppos->cy - pBorderSkin->GetMargin().bottom;
}

void SMenuWndHook::OnNcPaint()
{
    HDC dc = GetWindowDC(m_hWnd);
    OnPrint(dc);
    ReleaseDC(m_hWnd, dc);
}

void SMenuWndHook::OnPrint(HDC dc)
{
    if (m_strSkinName.IsEmpty())
        return;
    ISkinObj *pSkin = GETSKIN(m_strSkinName, 100);
    if (!pSkin)
        return;
    SSkinImgList *pBorderSkin = static_cast<SSkinImgList *>(pSkin);
    if (!pBorderSkin)
        return;

    CRect rcClient;
    GetClientRect(m_hWnd, &rcClient);
    ClientToScreen(m_hWnd, (LPPOINT)&rcClient);
    ClientToScreen(m_hWnd, ((LPPOINT)&rcClient) + 1);
    CRect rcWnd;
    GetWindowRect(m_hWnd, &rcWnd);
    rcClient.OffsetRect(-rcWnd.TopLeft());

    int nSave = ::SaveDC(dc);
    ::ExcludeClipRect(dc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
    rcWnd.MoveToXY(0, 0);

    SAutoRefPtr<IRenderTarget> pRT;
    GETRENDERFACTORY->CreateRenderTarget(&pRT, rcWnd.Width(), rcWnd.Height());
    pRT->BeginDraw();
    pBorderSkin->DrawByIndex(pRT, rcWnd, 0);
    pRT->EndDraw();
    HDC hmemdc = pRT->GetDC(0);
    ::BitBlt(dc, 0, 0, rcWnd.Width(), rcWnd.Height(), hmemdc, 0, 0, SRCCOPY);
    pRT->ReleaseDC(hmemdc, NULL);
    ::RestoreDC(dc, nSave);
}

void SMenuWndHook::OnNcDestroy()
{
    delete this;
}

//不能设计窗口半透明，设置区域后，非客户区位置发生改变，不明白原因。
void SMenuWndHook::OnWindowPosChanged()
{
    /*
        CRect rcWnd;
        GetWindowRect(m_hWnd,&rcWnd);
        rcWnd.MoveToXY(0,0);
        HRGN hRgn = ::CreateEllipticRgnIndirect(&rcWnd);
        SetWindowRgn(m_hWnd,hRgn,TRUE);
        DeleteObject(hRgn);
        */
}

SNSEND

#endif //_WIN32